﻿using System;
using System.Linq.Expressions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Postnuclear.Dependency;
using Postnuclear.Dependency.Tree;
using Postnuclear.Test.Helpers;

namespace Postnuclear.Test.Dependency.Tree
  {
  /// <summary>
  /// Represents a test class for the <see cref="DependencyTree"/> class.
  /// </summary>
  [TestClass]
  public sealed class DependencyTreeTest
    : TestClassBase
    {
    #region Public

    // Methods

    /// <summary>
    /// A test for <see cref="DependencyTree"/> constructor to check exception for null roots.
    /// </summary>
    [TestMethod]
    [ExpectedException(typeof(ArgumentNullException))]
    public void CreateWithNullRootsTest()
      {
      new DependencyTree(null);
      }

    /// <summary>
    /// A test for <see cref="INotifyValueChanged.ValueChanged" /> event.
    /// </summary>
    [TestMethod]
    public void ValueChangedTest()
      {
      Mock<IDependencyTreeElement<object>> target1 = new Mock<IDependencyTreeElement<object>>();
      Mock<IDependencyTreeElement<object>> target2 = new Mock<IDependencyTreeElement<object>>();
      Mock<IDependencyTreeElement<object>> target3 = new Mock<IDependencyTreeElement<object>>();
      DependencyTree dependencyTree = new DependencyTree(target1.Object, target2.Object, target3.Object);

      int updatesCount = 0;
      EventHandler<EventArgs> updateHandler = (_, __) => ++updatesCount;

      dependencyTree.ValueChanged += updateHandler;
      Assert.AreEqual(0, updatesCount);

      target1.RaiseValueChanged();
      Assert.AreEqual(1, updatesCount);

      target2.RaiseValueChanged();
      Assert.AreEqual(2, updatesCount);

      target3.RaiseValueChanged();
      Assert.AreEqual(3, updatesCount);

      dependencyTree.ValueChanged -= updateHandler;
      Assert.AreEqual(3, updatesCount);

      target1.RaiseValueChanged();
      Assert.AreEqual(3, updatesCount);

      target2.RaiseValueChanged();
      Assert.AreEqual(3, updatesCount);

      target3.RaiseValueChanged();
      Assert.AreEqual(3, updatesCount);
      }

    /// <summary>
    /// A test for <see cref="DependencyTree"/> is being strongly hold by target is it has subscriber and not being hold when there are no subscribers.
    /// </summary>
    [TestMethod]
    public void DependencyTreeGarbageCollectionTest()
      {
      IValueProvider<object> target = new Mock<IValueProvider<object>>().Object;

      Expression<Func<object>> expression = () => target.Value;
      INotifyValueChanged dependencyTree = expression.Observe();
      WeakReference<INotifyValueChanged> treeReference = new WeakReference<INotifyValueChanged>(dependencyTree);

      int updatesCount = 0;
      EventHandler<EventArgs> updateHandler = (_, __) => ++updatesCount;
      dependencyTree.ValueChanged += updateHandler;

      Assert.IsTrue(treeReference.IsAlive);

      dependencyTree = null;
      GarbageCollector.CollectAndWaitForPendingFinalizers();
      Assert.IsTrue(treeReference.IsAlive);

      treeReference.Target.ValueChanged -= updateHandler;
      GarbageCollector.CollectAndWaitForPendingFinalizers();
      Assert.IsFalse(treeReference.IsAlive);

      GC.KeepAlive(target);
      }

    /// <summary>
    /// A test for <see cref="DependencyTree"/> weak referencing the target.
    /// </summary>
    [TestMethod]
    public void TargetGarbageCollectionTest()
      {
      IValueProvider<object> target = new Mock<IValueProvider<object>>().Object;
      WeakReference<IValueProvider<object>> targetReference = new WeakReference<IValueProvider<object>>(target);

      Expression<Func<object>> expression = () => target.Value;
      INotifyValueChanged dependencyTree = expression.Observe();
      WeakReference<INotifyValueChanged> treeReference = new WeakReference<INotifyValueChanged>(dependencyTree);

      Assert.IsTrue(targetReference.IsAlive);
      Assert.IsTrue(treeReference.IsAlive);

      GC.KeepAlive(target);
      target = null;
      GarbageCollector.CollectAndWaitForPendingFinalizers();

      Assert.IsFalse(targetReference.IsAlive);
      Assert.IsTrue(treeReference.IsAlive);

      GC.KeepAlive(dependencyTree);
      dependencyTree = null;
      GarbageCollector.CollectAndWaitForPendingFinalizers();

      Assert.IsFalse(targetReference.IsAlive);
      Assert.IsFalse(treeReference.IsAlive);
      }

    #endregion
    }
  }
